import socket
import time
import signal
import errno
import threading

from threading import Thread, RLock
from SocketServer import ThreadingTCPServer, StreamRequestHandler
from Queue import Empty

from commandserver import command_send, command_recv
from coordinator import RequestQueue, Request
from debug import debug, format_exc

__all__ = [ "Participant" ]

MSG_ACK           = 'ack'
MSG_INFO          = 'i'
MSG_COORDINATOR   = 'c'
MSG_ELECTION      = 'e'
MSG_ANSWER        = 'n'
MSG_NOP           = 'nop'


class ParticipantRequest( Request ):
    pass
# ParticipantRequest


class SpreadParticipantRequest( ParticipantRequest ):
    """Participant request with hosts to spread the real request"""
    def __init__( self, command, arguments, spread_list ):
        ParticipantRequest.__init__( self, command, arguments )
        self.list = spread_list
    # __init__()
# SpreadParticipantRequest



class OutgoingHandler( object ):
    """Handles outgoing communication.

    Outgoing communication is what the local participant want to send to
    remote peers.

    It waits for requests in the request_queue.
    """
    def __init__( self, address, participant ):
        self.address = address
        self.request = None
        self.participant = participant
        self.is_connected = False

        self.commands = {
            MSG_COORDINATOR: self.do_coordinator,
            MSG_ELECTION: self.do_election,
            MSG_NOP: self.do_nothing,
            }

        self.thread = Thread( target=self.handle,
                              name="OutgoingHandler-%d" % \
                              self.inc_internal_counter() )
        self.thread.start()
    # __init__()


    internal_counter = 0
    def inc_internal_counter( co ):
        co.internal_counter += 1
        return co.internal_counter
    # inc_internal_counter()
    inc_internal_counter = classmethod( inc_internal_counter )


    def connect( self ):
        """Try to connect to remote peer.

        Returns just when connected or when server is stopped.
        """
        while self.participant.server.run:
            try:
                request = socket.socket( socket.AF_INET, socket.SOCK_STREAM )
                request.connect( self.address )
                request.settimeout( self.participant.timeout )
                self.request = request
                self.send_identifier()
                self.is_connected = True
                self.participant.run = True
                debug( 2, "Connected to %s:%s" % self.address )
                return

            except socket.error, e:
                if e[ 0 ] == errno.ECONNREFUSED:
                    debug( 3, "Connection to %s:%d refused." % \
                           self.address )
                    time.sleep( 1 )
                else:
                    debug( 3, "Error connecting to %s:%d: %s" % \
                           ( self.address + ( e, ) ) )
                    raise SystemExit( e )
    # connect()


    def send( self, command, data ):
        debug( 4, "SEND: %s( %r )" % ( command, data ) )
        return command_send( self.request.send, command, data )
    # send()


    def recv( self ):
        r = command_recv( self.request.recv )
        debug( 4, "RECV: %s( %r )" % ( r[ 0 ], r[ 1 ] ) )
        return r
    # recv()


    def send_identifier( self ):
        """Send local participant identifier to peer."""
        self.send( MSG_INFO, self.participant.server.address )
    # send_identifier()


    def handle( self ):
        # Run while participant is active
        # if disconnected, try to connect again
        while self.participant.server.run:
            self.is_connected = False
            self.connect() # waits until connected and identified

            while self.participant.run:
                request = self.participant.request_queue.get()
                try:
                    debug( 3, "Process request: %s" % request )
                    fun = self.commands[ request.command ]
                    v = fun( * request.arguments )

                except KeyError:
                    debug( 0, "Unknow command '%s', system is broken, exit." %\
                           request.command )
                    v = False
                    self.participant.run = False

                except socket.timeout, e:
                    v = False
                    self.participant.run = False
                    debug( 2, """\
------------------------------------------------------------
Socket operation timed out in %(timeout)s seconds.
Exception: %(exception)s
Traceback:

%(traceback)s

------------------------------------------------------------
""" % { 'timeout': self.participant.timeout,
        'exception': e,
        'traceback': format_exc() } )

                except socket.error, e:
                    v = False
                    self.participant.run = False
                    debug( 2, """\
------------------------------------------------------------
Socket error:
Exception: %(exception)s
Traceback:

%(traceback)s

------------------------------------------------------------
""" % { 'exception': e,
        'traceback': format_exc() } )


                # end try-except statement

                request.completed( v )
                if v == False:
                    debug( 2, "Request finished with False, exit." )
                    break
            # end while

            debug( 2, "End outgoing connection from: %s:%s" % self.address )
            self.request = None
            self.participant.run = False
            if self.participant.incoming:
                # End incoming connection too
                self.participant.incoming.request.close()
        # end while
    # handle()


    def do_coordinator( self, address ):
        """Inform other peer that this is the new coordinator"""
        debug( 3, "Inform new coordinator (%s:%s) to others: %s:%s" %
               ( address + self.address ) )
        self.send( MSG_COORDINATOR, address )
        return True
    # do_coordinator()


    def do_election( self ):
        """Call election.

        Send ELECTION, waits for ANSWER.
        """
        self.send( MSG_ELECTION, None )
        cmd, data = self.recv()
        if cmd == MSG_ANSWER:
            return True
        elif cmd is None:
            debug( 1, "Peer closed connection." )
            return False
        else:
            debug( 1, "Unknow reply to election: %s( %s )" % ( cmd, data ) )
            raise Exception( "Unknow reply to election: %s( %s )" % \
                             ( cmd, data ) )
    # do_election()

    def do_nothing( self ):
        """Nothing (aka No Operation)"""
        debug( 3, "NOP" )
        return True
    # do_nothing()
# OutgoingHandler










class IncomingHandler( StreamRequestHandler ):
    """Handles incoming communication.

    This will talk to the other peer OutgoingHandler and receive
    requests from it.

    Waits for requests on the socket.
    """
    def send( self, command, data ):
        debug( 4, "SEND: %s( %r )" % ( command, data ) )
        return command_send( self.request.send, command, data )
    # send()


    def recv( self ):
        r = command_recv( self.request.recv )
        debug( 4, "RECV: %s( %r )" % ( r[ 0 ], r[ 1 ] ) )
        return r
    # recv()


    def recv_identifier( self ):
        """Receive identifier from peer."""
        cmd, data = self.recv()
        assert( cmd == MSG_INFO )
        self.identifier = data
        debug( 3, "Peer identified as: %s:%s" % self.identifier )
    # send_identifier()


    def setup( self ):
        self.request.settimeout( self.server.timeout )
        self.commands = {
            MSG_COORDINATOR: self.do_coordinator,
            MSG_ELECTION: self.do_election,
            }

        debug( 2, "Got incoming connection from: %s:%s" % self.client_address )

        self.keep_running = True
        self.identifier = None
        self.recv_identifier()
        if self.identifier:
            self.server.add_incoming_participant( self.identifier, self )
            self.participant = self.server.participants[ self.identifier ]
            self.participant.run = True
        else:
            self.participant.run = False
    # setup()


    def finish( self ):
        debug( 2, "End incoming connection from: %s:%s (id: %s:%s)" % \
               ( self.client_address + self.identifier ) )
        if self.identifier:
            # End outgoing handler:
            self.participant.run = False
            outgoing = self.participant.outgoing
            if outgoing.request:
                outgoing.request.close()
                # Put a request (NoOPeration) to unblock OutgoingHanlder
                r = ParticipantRequest( MSG_NOP, tuple() )
                self.participant.add_request( r )

            self.server.reset_incoming_participant( self.identifier )
            if self.server.elected == self.identifier:
                self.server.elected = None
                debug( 2, "It was the coordinator. Call election again." )
                self.server.election()
    # finish()


    def handle( self ):
        if not self.identifier:
            debug( 1, "Unidentified peer, close connection." )
            return

        if self.server.elected < self.identifier:
            self.server.election()

        while self.participant.run and self.keep_running:
            try:
                cmd, data = self.recv()

            except socket.timeout:
                # Timeout is ignored here.
                continue

            except socket.error, e:
                if e[ 0 ] == errno.EBADF:
                    return
                else:
                    debug( 0, "Socket error: %s" % e )
                    return


            if cmd is None:
                debug( 1, "Got empty command from peer, close connection." )
                return

            try:
                fun = self.commands[ cmd ]
                fun( data )

            except KeyError:
                debug( 0, ( "Peer sent invalid command: %s( %s ). " \
                            "Close connection." ) % ( cmd, data ) )
                self.participant.run = False
                return

            except socket.timeout:
                debug( 0, ( "Peer timed out during execution of command: %s, "\
                            "data: %s" ) % ( cmd, data ) )
                self.participant.run = False
                return

            except socket.error, e:
                debug( 0, "Socket error: %s" % e )
                self.participant.run = False
                return
    # handle()


    def do_coordinator( self, address ):
        """Setup new coordinator."""
        debug( 2, "New coordinator: %s:%d" % address )
        if address > self.server.address:
            if address >= self.server.elected:
                self.server.elected = address
            else:
                debug( 2, "Coordinator seems to be up, but got a new one " \
                       "with lower identifier." )
                self.server.election()

        else:
            # Weird, call election again
            debug( 0, "Got coordinator with id lower than mine, call " \
                   "election again" )
            self.server.election()
    # do_coordinator()


    def do_election( self, ignored ):
        """Do election."""
        self.send( MSG_ANSWER, None )
        debug( 2, "Others call for elections." )
        if not self.server.in_election:
            self.server.in_election = True
            self.server.election()
            self.server.in_election = False
        else:
            debug( 2, "Already in election. Ignore request." )
    # do_election()
# IncomingHandler




class OtherParticipant( object ):
    """Placeholder for both communications with participant.

    Every participant have 2 communication channels (2 sockets):
      * Incoming: it's a L{IncomingHandler} that get requests from peers and
        executes locally.
      * Outgoing: it's a L{OutgoingHandler} that send requests to peers.
    """
    def __init__( self, address, server, incoming=None, outgoing=None ):
        self.address  = address
        self.incoming = incoming
        self.outgoing = outgoing
        self.server   = server
        self.request_queue = RequestQueue()
        self.__run = True

        self.connect()
    # __init__()


    def get_run( self ):
        return self.server.run and self.__run
    # get_run()
    def set_run( self, v ):
        self.__run = v
    # set_run()
    run = property( get_run, set_run )


    def get_timeout( self ):
        return self.server.timeout
    # get_timeout()
    timeout = property( get_timeout )


    def connect( self ):
        self.outgoing = OutgoingHandler( self.address, self )
    # connect()


    def add_request( self, request ):
        if self.outgoing.is_connected:
            self.request_queue.put( request )
            return True

        else:
            return False
    # add_request()
# OtherParticipant







class Participant( ThreadingTCPServer, object ):
    """Represents the participant in the election.

    This is also a server that launches IncomingHandler for every peer that
    connects.

    It launches a OutgoingHandler in a separate thread that will try to
    connect to peer.

    Peers not in the initial peers list will be added as they connect.
    """
    allow_reuse_address = True
    timeout_trans = 2
    timeout_proc  = 2
    timeout = timeout_trans + timeout_proc


    def __init__( self, address, elected_callback=None, peers=None ):
        self.address = address
        self.elected_callback = elected_callback
        self.run = True
        self.request_queue = RequestQueue()
        self.__elected = None
        self.in_election = False

        self.participants = {}

        self.__incoming_threads = {}

        ThreadingTCPServer.__init__( self, address, IncomingHandler )

        self.internal_handler_thread = Thread( name="Internal",
                                               target=self.internal_handler )
        self.internal_handler_thread.start()

        self.connect( peers or [] )
    # __init__()


    def get_elected( self ):
        return self.__elected
    # get_elected()
    def set_elected( self, elected ):
        self.__elected = elected

        if self.elected_callback:
            self.elected_callback( elected )
    # set_elected()
    elected = property( get_elected, set_elected )


    def turn_into_coordinator( self ):
        debug( 2, "I'm the new coordinator" )
        pl = self.participants.keys()
        self.elected = self.address
        r = SpreadParticipantRequest( MSG_COORDINATOR,
                                      ( self.address, ), pl )

        self.request_queue.put( r )
        debug( 2, "Inform others that i'm the coordinator" )
        v = r.wait()
        return v
    # turn_into_coordinator()


    def ask_others_who_coordinates( self ):
        debug( 2, "I don't know who is the coordinator, " \
               "calling election." )

        pl = self.participants.keys()
        pl.sort()
        high = []
        low  = []
        for p in pl:
            if p < self.address:
                low.append( p )
            else:
                high.append( p )
        r = SpreadParticipantRequest( MSG_ELECTION, tuple(), high )

        self.request_queue.put( r )
        debug( 2, "Wait for election to finish..." )
        v = r.wait()

        assert( isinstance( v, ( list, tuple ) ) )

        if len( v ) == 0:
            debug( 2, "No higher peer is connected, I'm the coordinator" )
            v = self.turn_into_coordinator()
        else:
            debug( 2, "Check if peers failed" )
            l = []
            for idx, p in enumerate( high ):
                if not v[ idx ]:
                    l.append( p )

            if l == high:
                debug( 1, "Every higher id process failed, " \
                       "I'm the coordinator." )
                self.turn_into_coordinator()

        debug( 2, "Election finished. Coordinator is %s" % ( self.elected, ) )
        return v
    # ask_others_who_coordinates()


    def election( self ):
        pl = self.participants.keys()
        pl.sort()
        v = None

        if not pl:
            debug( 2, "There are no other peers, I'm the coordinator" )
            self.elected = self.address
            v = None
        else:
            greatest = pl[ -1 ]
            if greatest < self.address:
                v = self.turn_into_coordinator()
            else:
                v = self.ask_others_who_coordinates()

        return v
    # election()



    def internal_handler( self ):
        while self.run:
            r        = self.request_queue.get()
            requests = self.spread_request( r.list, r.command, r.arguments )
            retval   = self.wait_requests_completion( requests )
            r.completed( retval )
    # internal_handler()


    def spread_request( self, elements, *args, **kargs ):
        requests = []

        for address in elements:
            r = ParticipantRequest( *args, **kargs )
            participant = self.participants[ address ]
            fargs = address + ( r, )

            if participant.add_request( r ):
                debug( 2, "Spread request to: %s:%s: %s" % fargs )
            else:
                debug( 2, ( "Outgoing Handler for %s:%s is down, " \
                            "request not spread: %s" % fargs ) )
                # Fake request
                r = ParticipantRequest( MSG_NOP, None )
                r.completed( False )

            requests.append( r )

        return requests
    # spread_request()


    def wait_requests_completion( self, requests ):
        retvals = []
        for req in requests:
            ret = req.wait()
            retvals.append( ret )
            if ret == False or \
               ( isinstance( ret, ( list, tuple ) ) and False in ret ):
                debug( 2, "Peer failed: %s with return: %s" % ( req, ret ) )

        return retvals
    # wait_requests_completion()


    def connect( self, peers ):
        for address in peers:
            self.add_incoming_participant( address, None )
    # connect()


    def add_incoming_participant( self, address, incoming ):
        try:
            participant = self.participants[ address ]
            if participant.incoming:
                participant.incoming.keep_running = False
                try:
                    t = self.__incoming_threads[ participant.incoming.
                                                 client_address ]
                    debug( 2, ( "Wait for current incoming thread (%s/%s:%s) "\
                                "to finish." ) % ( ( t, ) + client_address ) )
                    t.join()
                except Exception:
                    pass
            # end if participant.incoming
            participant.incoming = incoming

        except KeyError:
            self.participants[ address ] = OtherParticipant( address, self )
    # add_incoming_participant()


    def reset_incoming_participant( self, address ):
        self.add_incoming_participant( address, None )
    # reset_incoming_participant()


    def process_request( self, request, client_address ):
        # Overload to change thread's name
        self.process_request_lock.acquire()
        self.process_request_number += 1
        tid = self.process_request_number
        self.process_request_lock.release()

        t = threading.Thread( target=self.process_request_thread,
                              args=(request, client_address),
                              name="IncomingHandler-%d" % tid )
        if self.daemon_threads:
            t.setDaemon( 1 )

        self.__incoming_threads[ client_address ] = t

        t.start()
    # process_request()
    process_request_number = 0
    process_request_lock   = threading.RLock()
# Participant




if __name__ == "__main__":
    import sys
    p = sys.argv[ 1: ]
    if len( p ) % 2 != 0:
        raise SystemExit( "invalid argument number." )

    hosts = []
    c = 0
    while c < len( p ):
        host = p[ c ]
        port = int( p[ c + 1 ] )
        hosts.append( ( host, port ) )
        c += 2

    own = hosts[ 0 ]
    others = hosts[ 1 : ]

    def print_elected( elected ):
        print ">" * 79
        print "Elected: %s" % ( elected, )
        print "<" * 79
    # print_elected()

    debug.level = 1
    p = Participant( own, print_elected, others )

    t = Thread( name="Server", target=p.serve_forever )
    t.start()

    #time.sleep( 1 )
    x = False
    while True:
        if not p.elected:
            p.election()
        time.sleep( 1 )
